package com.topchain.licenceverify.service;

import com.topchain.licence.LicenceContent;
import com.topchain.licenceverify.bean.LicenceProperties;
import com.topchain.licenceverify.bean.RSA;
import com.topchain.licenceverify.utils.IPUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

/**
 * @DESCRIPTION: 校验licence文件是否有效
 * @USER: shg
 * @DATE: 2023/4/11 15:02
 */

public class LicenceVerify implements ApplicationRunner, ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(LicenceVerify.class);
    private static final String KEY_ALGORITHM_256 = "SHA256withRSA";

    @Autowired
    LicenceProperties licenceProperties;

    private ApplicationContext applicationContext;

    /**
     * 项目启动时，自动调用这个方法校验licence文件的有效性
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {

        if (!licenceProperties.isEnable()) {
            log.info("未开启licence校验功能...");
            return;
        }

        log.info("开启licence校验功能...");
        String licencePath = licenceProperties.getLicencePath();
        if (!StringUtils.hasText(licencePath)) {
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("请检查licence文件是否配置正确...");
        }
        log.info("licencePath:{}", licencePath);

        LicenceContent licenceContent;
        try {
            // 反序列化的licence文件
            ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(licenceProperties.getLicencePath())));
            licenceContent = (LicenceContent) objectInputStream.readObject();
        } catch (Exception e) {
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("读取licence文件失败...");
        }
        
        // 1. 校验用户证书有效性
        checkCertificate(licenceContent.getRootCertContent(), licenceContent.getUserCertContent());

        // 2. 验证licence的签名有效性
        verifySign(licenceContent, licenceContent.getUserCertContent());
        
        // 3. 验证licence是否在有效期内
        verifyDate(licenceContent);

        // 4. 校验ip是否在白名单中
        if (licenceProperties.isVerifyIP() && !verifyIp(licenceContent)) {
            log.error("IP error, only allow: " + licenceContent.getAllowIp());
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("licence校验不通过，原因：当前服务器ip地址校验未通过...");
        }

        // 5. 校验mac地址是否在白名单中
        if (licenceProperties.isVerifyMac() && !verifyMac(licenceContent)) {
            log.error("mac error, only allow: " + licenceContent.getMac());
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("licence校验不通过，原因：当前服务器mac地址校验未通过...");
        }
        log.info("License verification passed.");
    }

    private void checkCertificate(String rootCert, String userCert) {
        try {
            // 获取根证书
            X509Certificate x509RootCertificate = getX509Certificate(rootCert);
            // 获取用户证书
            X509Certificate x509UserCertificate = getX509Certificate(userCert);
            // 验证用户证书的有效性
            x509UserCertificate.verify(x509RootCertificate.getPublicKey());
            x509UserCertificate.checkValidity();
        } catch (Exception e) {
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("您正在使用的licence文件非法。原因：校验证书有效性失败...");
        }
    }

    private void verifySign(LicenceContent licenceContent, String userCert) {
        try {
            PublicKey publicKey = getX509Certificate(userCert).getPublicKey();
            String sb = licenceContent.getUser() + licenceContent.getAllowIp() + licenceContent.getMac() + licenceContent.getNotBeforeTime() + licenceContent.getNotAfterTime();
            RSA rsa = new RSA();
            boolean verify = rsa.verify(sb.getBytes(), publicKey, Base64.getDecoder().decode(licenceContent.getSign().getBytes()), KEY_ALGORITHM_256);
            if (!verify) {
                SpringApplication.exit(applicationContext, () -> 0);
                throw new RuntimeException("您正在使用的licence文件非法。原因：验证licence签名失败...");
            }
        } catch (Exception e) {
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException(e);
        }
    }

    // 把Base64格式的pem文件转换成Certificate对象
    public static X509Certificate getX509Certificate(String certificateName) throws IOException, CertificateException {
        InputStream inputStream = new ByteArrayInputStream(certificateName.getBytes());
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        X509Certificate certificate = null;
        while (bufferedInputStream.available() > 0) {
            certificate = (X509Certificate) certificateFactory.generateCertificate(bufferedInputStream);
        }
        return certificate;
    }

    // 校验时间有效期
    private void verifyDate(LicenceContent licenceContent) {
        Date currentTime = new Date();
        if (currentTime.before(new Date(licenceContent.getNotBeforeTime()))) {
            log.error("licence未到生效时间: " + licenceContent.getNotBeforeTime());
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("licence未到生效时间,生效时间为：" +
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(licenceContent.getNotBeforeTime())));
        }

        if (currentTime.after(new Date(licenceContent.getNotAfterTime()))) {
            log.error("licence已过期: " + licenceContent.getNotAfterTime());
            SpringApplication.exit(applicationContext, () -> 0);
            throw new RuntimeException("licence已到期，请联系licence签发方重新签发licence文件，联系信息：<邮箱：snl@dinglianshuke.com>,<电话：0755-89662748>");
        }
    }

    // 校验ip白名单
    private static boolean verifyIp(LicenceContent licenceContent) throws SocketException, UnknownHostException {
        List<String> localIps = IPUtils.getLocalIp();
        boolean hasAllowIp = localIps.contains(licenceContent.getAllowIp());
        log.info("localIps:{}", localIps);
        return hasAllowIp;

    }

    // 校验mac白名单
    private boolean verifyMac(LicenceContent licenceContent) throws SocketException {
        String mac = getMac();
        log.info("mac:{}:", mac);
        return mac.contains(licenceContent.getMac());
    }

    private String getMac() throws SocketException {
        StringBuilder sb = new StringBuilder();
        Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
        byte[] mac;
        while (allNetInterfaces.hasMoreElements()) {
            NetworkInterface netInterface = allNetInterfaces.nextElement();
            if (netInterface.isLoopback() || netInterface.isVirtual() || netInterface.isPointToPoint() || !netInterface.isUp()) {
                continue;
            } else {
                mac = netInterface.getHardwareAddress();
                if (mac != null) {
                    for (int i = 0; i < mac.length; i++) {
                        sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : "\t"));
                    }
                }
            }
        }
        return sb.toString();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        log.info("applicationContext:{}", applicationContext);
    }

    // public static void main(String[] args) throws CertificateException, IOException, SignException {
    //
    //     String userCertPath = "C:\\Users\\24350\\Desktop\\G2\\28-licence方案\\曙光云licence相关备份\\StandardUserCert.pem";
    //     PublicKey publicKey = getX509Certificate(userCertPath).getPublicKey();
    //
    //     String source = "shuguangyun192.168.5.3138-CA-84-46-C2-17Thu Dec 28 00:00:00 GMT+08:00 2023Thu Jun 27 23:59:59 GMT+08:00 2024";
    //
    //     String sign = "F+DQXi1lfoFAzP7grERgd2zuk7fFZ0xFDVgdPR5sUDuXuwisezJXWDyzCry3MaZeuhwGCauO+++dQnyns+TjYLNLr1x3Xw+79u2v8940BSWPLUvBjCpNz2H466UWSD7dX3CYexnnGO94jOMfDz0n9hrW3RNx+ei4V/lkECuDGr+H+Tsvw1DJsr41urWc9DWwA7eycEuAXLeNrav0zNUYUvpkNIfjtQe3eBlLwU1REicHqZjFaUI9qwTs6+BO9xeoOnyUlMTxmV19fe4r2W6VWf6WDAguBu4N0TOHtGyGp/Ti32NZM/zB7EOWhFnvLA2h1S4P2EsDhgrUtxoNWZhpPA==";
    //
    //     RSA rsa = new RSA();
    //     boolean verify = rsa.verify(source.getBytes(), publicKey, Base64.getDecoder().decode(sign.getBytes()), KEY_ALGORITHM_256);
    //     System.out.println(verify);
    // }
}
